﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Timers;
using System.Diagnostics;
using System.Text;

namespace Airport
{
    public class EnvironmentUpdater
    {
        private AirportService _airportSVC;

        public EnvironmentUpdater(AirportService airportSVC)
        {
            _airportSVC = airportSVC;

            Timer worldTimer = new Timer(1000);
            worldTimer.Elapsed += new ElapsedEventHandler(UpdatePlanes);
            worldTimer.Start();
        }

        public void UpdatePlanes(object source, ElapsedEventArgs e)
        {
            lock (_airportSVC._activePlanesLock)
            {
                //Debug.WriteLine("** EnvironmentUpdater: UpdatePlanes **");
                for (int i = 0; i < _airportSVC.ActivePlanes.Count; i++)
                {
                    _airportSVC.ActivePlanes[i] = UpdatePlane(_airportSVC.ActivePlanes[i]);
                }
            }
            _airportSVC.UpdateAllPlanePositions();
        }

        public Plane UpdatePlane(Plane plane)
        {            
            //Debug.WriteLine("** EnvironmentUpdater: UpdatePlane **");
            DateTime thisUpdate = DateTime.Now;
            float t = (float)((thisUpdate - plane.LastUpdate).TotalMilliseconds / 1000);
            plane.LastUpdate = thisUpdate;
            WorldVector previousPosition = plane.PositionVector;
            float updatedDifferenceX, updatedDifferenceY;
            float nextRouteNodeDifferenceX, nextRouteNodeDifferenceY;

            // Check if plane status is ClearedForTakeoff
            if (plane.CurrentRoute != null 
                && plane.CurrentRoute.RouteNodes.Count > 0)
            {
                if (plane.Status == PlaneStatus.ClearedForTakeoff)
                {
                    plane.Status = PlaneStatus.TakingOff;
                    WorldVector nextNodeVector = new WorldVector(plane.PositionVector, plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex]);

                    // Set ForceVector to accelerate the plane
                    plane.ForceVector = nextNodeVector.UnitVector.Times((float)(plane.TakeoffThrust));
                }
                else if (plane.Status == PlaneStatus.TakingOff)
                {

                    // If speed at or above takeoff speed, set force to zero
                    if (plane.VelocityVector.Magnitude >= plane.TakeoffSpeed)
                    {
                        plane.ForceVector = new WorldVector(0, 0, 0);
                    }
                }
                else if (plane.Status == PlaneStatus.ClearedForLanding)
                {
                    plane.Status = PlaneStatus.Landing;

                    WorldVector nextNodeVector = new WorldVector(plane.PositionVector, plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex]);

                    // Divide Edge's average speed by the map scale factor to convert m/s to px/s
                    float currentEdgeAverageSpeed = (float)(77.0 / _airportSVC.MapScaleFactor);

                    WorldVector newVelocityVector = nextNodeVector.UnitVector.Times(currentEdgeAverageSpeed);

                    plane.VelocityVector = newVelocityVector;

                    // Set ForceVector to decelerate the plane
                    plane.ForceVector = nextNodeVector.UnitVector.Times((float)(plane.ReverseThrust / _airportSVC.MapScaleFactor));
                }
                else if (plane.Status == PlaneStatus.Landing)
                {
                    float taxiSpeed = (float)(34.0 / _airportSVC.MapScaleFactor);

                    // If speed below taxi speed, set force to zero
                    if (plane.VelocityVector.Magnitude <= taxiSpeed)
                    {
                        plane.ForceVector = new WorldVector(0, 0, 0);

                        // Set plane status to Taxiing
                        plane.Status = PlaneStatus.Taxiing;
                    }
                }
            }

            // a = f / m
            plane.AccelerationVector = plane.ForceVector.Times(1 / plane.Mass);

            // v = at
            plane.VelocityVector = plane.VelocityVector.Plus(plane.AccelerationVector.Times(t));

            // x = vt + 1/2 * at^2
            plane.PositionVector = plane.PositionVector.Plus(plane.VelocityVector.Times(t).Plus(plane.AccelerationVector.Times(t * t / 2)));

            // Check if the Plane has gone off of its last Edge
            // For now, we assume this means it went to another Edge
            if (!plane.CurrentEdge.ContainsPositionVector(plane.PositionVector))
            {
                // Find what edge the Plane is currently on
                List<Edge> matchingEdges = (from edge in _airportSVC.AirportEdges
                                            where edge.ContainsPositionVector(plane.PositionVector)
                                            select edge).ToList();
                
                if (matchingEdges.Count == 0)
                {
                    throw (new Exception("Cannot find Plane's CurrentEdge in Map"));
                }

                string matchingEdgesString = "";
                foreach (Edge edge in matchingEdges)
                {
                    matchingEdgesString += edge.ToString() + ", ";
                }
                Debug.WriteLine("EnvironmentUpdater: Plane on New Edge(s): " + matchingEdgesString);

                // If plane is on more than 1 Edge, favor the previous CurrentEdge
                if (matchingEdges.Count == 1)
                {
                    // Check if previous Edge was a runway
                    if (IsRunway(plane.CurrentEdge))
                    {
                        if (!IsRunway(matchingEdges[0]))
                        {
                            // Divide Edge's average speed by the map scale factor to convert m/s to px/s
                            float currentEdgeAverageSpeed = (float)(((Edge)matchingEdges[0]).AverageSpeed / _airportSVC.MapScaleFactor);

                            WorldVector newVelocityVector = plane.VelocityVector.UnitVector.Times(currentEdgeAverageSpeed);

                            plane.VelocityVector = newVelocityVector;
                        }
                    }

                    plane.CurrentEdge = matchingEdges[0];
                }
            }
            
            // If the CurrentRoute's start time has elapsed
            if (thisUpdate >= plane.CurrentRoute.StartTime)
            {
                // If the Plane's next RouteNodeIndex is -1, it has no Route and should stop. If the Plane is at the end of 
                // its Route, it should stop.
                if ((plane.NextRouteNodeIndex > -1) && (plane.NextRouteNodeIndex < plane.CurrentRoute.RouteNodes.Count - 1))
                {
                    updatedDifferenceX = Math.Abs(previousPosition.X - plane.PositionVector.X);
                    updatedDifferenceY = Math.Abs(previousPosition.Y - plane.PositionVector.Y);
                    nextRouteNodeDifferenceX = Math.Abs(previousPosition.X - plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex].XPos);
                    nextRouteNodeDifferenceY = Math.Abs(previousPosition.Y - plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex].YPos);
                    
                    // Plane is at the beginning of its Route
                    //if (plane.NextRouteNodeIndex == 0)
                    // TODO Have this check if the plane is at a gate. Zero velocity doesn't necessarily mean it's at the beginning
                    // of the Route
                    if (plane.VelocityVector.Magnitude == 0)
                    {
                        //Debug.WriteLine("* Plane at Beginning of Route");
                        // The plane is stopped at its starting node, give it an initial velocity
                        //WorldVector nextNodeVector = new WorldVector(plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex], 
                        //                                    plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex + 1]);                    
                        WorldVector nextNodeVector = new WorldVector(plane.PositionVector, plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex]);

                        Edge currentEdge = (from edge in _airportSVC.AirportEdges
                                            where edge == plane.CurrentEdge
                                            select edge).FirstOrDefault();

                        // Divide Edge's average speed by the map scale factor to convert m/s to px/s
                        float currentEdgeAverageSpeed = (float)(currentEdge.AverageSpeed / _airportSVC.MapScaleFactor);

                        WorldVector newVelocityVector = nextNodeVector.UnitVector.Times(currentEdgeAverageSpeed);
                       
                        plane.VelocityVector = newVelocityVector;
                        plane.Heading = (int)Math.Round(newVelocityVector.Direction);
                    }
                    else
                    {
                        string newVelocityMessageString = "EnvironmentUpdater: Changing Velocity\n";
                        newVelocityMessageString += "Old Position Vector: " + previousPosition.ToString() + "\n";
                        newVelocityMessageString += "Old Velocity Vector: " + plane.VelocityVector.ToString() + "\n";
                        newVelocityMessageString += "Old Heading: " + plane.Heading + "\n";
                        
                        WorldVector nextNodeVector = new WorldVector(plane.PositionVector, plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex]);
                        newVelocityMessageString += "nextNodeVector: " + nextNodeVector.ToString() + "\n";
                        newVelocityMessageString += "plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex]: " + plane.CurrentRoute.RouteNodes[plane.NextRouteNodeIndex].ToString() + "\n";
                        float oldVelocityMagnitude = (float)plane.VelocityVector.Magnitude;

                        WorldVector newVelocityVector = nextNodeVector.UnitVector.Times(oldVelocityMagnitude);

                        plane.VelocityVector = newVelocityVector;
                        plane.Heading = (int)Math.Round(newVelocityVector.Direction);

                        newVelocityMessageString += "New Position Vector: " + plane.PositionVector.ToString() + "\n";
                        newVelocityMessageString += "New Velocity Vector: " + plane.VelocityVector.ToString() + "\n";
                        newVelocityMessageString += "New Heading: " + plane.Heading + "\n";

                        //Debug.WriteLine(newVelocityMessageString);
                    }
                }
                else
                {
                    // Set plane's velocity to zero
                    plane.VelocityVector = new WorldVector();
                }
            }
            if (plane.CurrentRoute != null)
            {
                //plane.CurrentRoute.StartPoint = new Point((int)plane.PositionVector.X, (int)plane.PositionVector.Y);
            }

            //_airportSVC.UpdatePlanePosition(plane);
            
            /*
            Debug.WriteLine("[" + DateTime.Now.ToString("hh:mmtt") + "] " + plane.Name 
                                + " {\n\t Pos: " + plane.PositionVector + " m\n\t Vel: " + plane.VelocityVector 
                                + " m/s\n\t Acc: " + plane.AccelerationVector + " m/s^2\n\t For: " + plane.ForceVector + " N\n}\n");
            */
            return plane;
        }
        
        private bool IsRunway(Edge edge)
        {
            return edge.Name.Equals("6L/24R", StringComparison.CurrentCultureIgnoreCase) ||
                    edge.Name.Equals("6R/24L", StringComparison.CurrentCultureIgnoreCase) ||
                    edge.Name.Equals("7L/25R", StringComparison.CurrentCultureIgnoreCase) ||
                    edge.Name.Equals("7R/25L", StringComparison.CurrentCultureIgnoreCase);
                
        }
    }
}